package br.ufrn.dimap.ase.squid.metrics.visitors;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.resources.IProject;
import org.eclipse.emf.common.util.EList;

import br.ufrn.dimap.ase.squid.core.models.squidmodel.Asset;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.Feature;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.Field;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.Mapping;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.Method;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.impl.ClassImpl;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.impl.CodePieceImpl;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.impl.FieldImpl;
import br.ufrn.dimap.ase.squid.core.models.squidmodel.impl.MethodImpl;
import br.ufrn.dimap.ase.squid.metrics.explorer.Explorador;
import br.ufrn.dimap.ase.squid.metrics.tamanho.Contador;
import br.ufrn.dimap.ase.squid.metrics.tamanho.ProcessadorLinha;

public class VisitorMetricTamanho extends AbstractVisitor {
	
	private IProject project;
	private Contador contador;
	private Map<String, List<Mapping>> mapping;
	
	/** Feature - Pacotes */
	private Map<String, List<String>> mappingPacotes;
	/** Feature - classes */
	private Map<String, List<Mapping>> mappingClasses;

	private Explorador explorador;
	
	/** Lines of Code */
	private int loc;
	/** Lines of Code the Feature */
	private int locFeature;
	
	/** Number of Packages */
	private int nop;
	
	/** Number of Classes */
	private int noc;
	
	/** Lines of Features */
	private int lof;
	
	/** Mapeia as features com o numero de linhas que ela tem */
	private Map<String, Integer> numberLinesOfFeatures;
	
	/** Feature que sera calculada */
	private String feature;
	
	public VisitorMetricTamanho(IProject project, Map<String, List<Mapping>> mapping, String feature) {
		this.project = project;
		this.mapping = mapping;
		this.feature = feature;
		
		contador = new Contador();
		
		explorador = new Explorador(getDirectoryOfLPS());
		
		this.mappingClasses = new HashMap<String, List<Mapping>>();
		this.mappingPacotes = new HashMap<String, List<String>>();
	}
	
	public void visit() {
		
		this.loc = getTotalLinhasLPS();
		this.locFeature = getTotalLinhasFeature(feature);
		numberLinesOfFeatures = new HashMap<String, Integer>();
		Iterator<String> it = mapping.keySet().iterator();
		
		populeMappingClasses();
		popularMappingPacotes();
		
		tirarRepeticoes();
		
		while (it.hasNext()) {
			String featureName = it.next();
			
			if (!numberLinesOfFeatures.containsKey(featureName)){
				numberLinesOfFeatures.put(featureName, getTotalLinhasFeature(featureName));
			}
		}
		
//		int numeroLinhasFeature = getTotalLinhasFeature("Sorting");
		
		
//		int a = 9;
	}
	
	public void tirarRepeticoes() {
		Set<String> keysClasses = mappingClasses.keySet();
		
		for(String key : keysClasses) {
			this.tirarRepeticoesListasMapping(mappingClasses.get(key));
		}
		
		Set<String> keysPacotes = mappingPacotes.keySet();
		for(String key : keysPacotes) {
			this.tirarRepeticoesListasString(mappingPacotes.get(key));
		}
	}
	
	/**
	 * Cria o mapeamentos de classes por feature
	 * 	Todas as classes complemente anotadas, para determinada feature
	 */
	private void populeMappingClasses() {
		Set<String> keys = mapping.keySet();
		
		for(String key : keys) {
			List<Mapping> maps = mapping.get(key);
			mappingClasses.put(key, new ArrayList<Mapping>());
			
			for(Mapping map : maps) {
				if(map.getAssets() instanceof ClassImpl) {
					mappingClasses.get(key).add(map);
				}
			}
		}
	}
	
	/**
	 * Cria os mapeamentos de pacotes por feature
	 * 	Todos os pacotes completamente anotados por uma feature,
	 * 	ou seja, os pacotes que tem 
	 */
	private void popularMappingPacotes() {
		
		Set<String> keys = mapping.keySet();
		
		for(String feature : keys) {
			List<Mapping> maps = mapping.get(feature);
			if(!mappingPacotes.containsKey(feature))
				mappingPacotes.put(feature, new ArrayList<String>());
			
			for(Mapping map : maps) {
				if(map.getAssets() instanceof ClassImpl) {
					addPacote(map.getAssets(), explorador.getMapping(), feature);
				}
			}
			
			soTotalmenteAnotado(feature);
		}
	}
	
	/**
	 * Deixar em mappingPacotes apenas os pacotes q tem classes totalmente anotadas
	 */
	public void soTotalmenteAnotado(String feature) {
		List<String> pacotes = mappingPacotes.get(feature);
		
//		for(String feature : features) {
//			List<String> pacotes = mappingPacotes.get(feature);
			
			if(pacotes != null) {
				for(int i = 0; i < pacotes.size(); i++) {
					List<String> classes = getClassesDePacotes(pacotes.get(i));
					
					for(String classe : classes) {
						if(!isAnotada(classe, feature)) {
							mappingPacotes.get(feature).remove(pacotes.get(i));
							i--;
							break;
						}
					}
				}
			}
//		}
	}
	
	/**
	 * Informa se uma classe eh completamente anotada pela feature passada por parametro
	 * 
	 * @param classe
	 * @return
	 */
	public boolean isAnotada(String classe, String feature) {
		Set<String> keys = this.mapping.keySet();
		
		if(classe.contains(".java"))
			classe = classe.substring(0, classe.indexOf(".java"));
		
		for(String key : keys) {
			List<Mapping> maps = mapping.get(key);
			
			for(Mapping map : maps) {
				EList<Feature> features = map.getFeatures();
				boolean mesmaFeature = false;
				for(int i = 0; i < features.size(); i++) {
					if(features.get(i).getName().equalsIgnoreCase(feature)) 
						mesmaFeature = true;
				}
				if(!mesmaFeature)
					continue;
				
				if(map.getAssets() instanceof ClassImpl) {
					String sMap = null;
					if(map.getName().contains(".java"))
						sMap = map.getName().substring(0, map.getName().indexOf(".java"));
					else 
						sMap = map.getName();
					
					if(sMap.equalsIgnoreCase(classe))
						return true;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * Retorna as classes de um pacote
	 * 
	 * @param pacote
	 * @return
	 */
	private List<String> getClassesDePacotes(String pacote) {
		List<String> classes = new ArrayList<String>();
		
		pacote = getDirectoryOfLPS() + "/src/" + pacote.replace(".", "/");
		
		File file = new File(pacote);
		if(file.isDirectory()) {
			File[] files = file.listFiles();
			for(File f : files) {
				if(f.isFile() && f.getName().endsWith(".java"))
					classes.add(f.getName());
			}
		}
		
		return classes;
	}
	
	/**
	 * TODO
	 * 	TIRAR o ".java"
	 * 
	 * @param _class
	 * @param mappingsExplorer
	 * @param feature
	 */
	private void addPacote(Asset _class, Map<String, List<File>> mappingsExplorer, String feature) {
		Set<String> keys = mappingsExplorer.keySet();
		
		for(String key : keys) {
			List<File> maps = mappingsExplorer.get(key);
			for(File map : maps) {
				if(_class.getName().equalsIgnoreCase(map.getName())) {
					mappingPacotes.get(feature).add(_class.getPath());
				}
			}
		}
	}
	
	private int getTotalLinhasFeature(String... features){
		int total = 0;
		
		List<Mapping> maps = new ArrayList<Mapping>();
		for(String feature : features){
			if (mapping.containsKey(feature)) {
				maps.addAll( mapping.get(feature) );
			}
		}
		
		for(Mapping map : maps){
			total += this.calcularTotalLinhasMap(map.getAssets());
		}
		
		return total;
	}
	
	private int calcularTotalLinhasMap(Asset map){
		if(map instanceof CodePieceImpl)
			return this.getNumeroLinhasMap((CodePieceImpl) map);
		if(map instanceof ClassImpl)
			return this.getNumeroLinhasMap((ClassImpl) map);
		if(map instanceof FieldImpl)
			return this.getNumeroLinhasMap((FieldImpl) map);
		if(map instanceof MethodImpl) 
			return this.getNumeroLinhasMap((MethodImpl) map);
		
		return 0;
	}
	private int getNumeroLinhasMap(CodePieceImpl codePieceImpl) {
		System.out.println("## CODEPIECE " + codePieceImpl.getSignature());
		int numeroLinhasCode = 0;
		String[] code = codePieceImpl.getCode().substring(1).split("\n");
		
		numeroLinhasCode += contador.getNumberLines(code);
		
		return numeroLinhasCode;
	}
	private int getNumeroLinhasMap(ClassImpl classImpl) {
		System.out.println("## CLASS ");
		int numeroLinhasFields = 0;
		EList<Field> fields = classImpl.getDeclaredFields();
		for(int i = 0; i < fields.size(); i++){
			numeroLinhasFields += this.getNumeroLinhasMap( (FieldImpl) fields.get(i));
		}
		
		int numeroLinhasMethods = 0;
		EList<Method> methods = classImpl.getDeclaredMethods();
		for(int i = 0; i < methods.size(); i++){
			numeroLinhasMethods += this.getNumeroLinhasMap( (MethodImpl) methods.get(i));
		}
		
		return numeroLinhasFields + numeroLinhasMethods;
	}
	private int getNumeroLinhasMap(FieldImpl fieldImpl) {
		ProcessadorLinha processadorLinha = new ProcessadorLinha();
		System.out.println("## Field " + fieldImpl.getName() + " " + fieldImpl.getPath() + " " + fieldImpl.getSignature());
		
		File file = getFileOfPath(fieldImpl.getPath());
		String strAnterior = "";
		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			contador.init();
			while (in.ready()) {
				str = in.readLine();

				str = contador.getRealLine(str);
				str = str.replace("\t", " ");
//				Pattern p = Pattern.compile("[ ]*(public|private|protected)[ ]*(static)?[ ]*(final)?[ ]*[a-zA-Z]*[ ]*.*\\n" + "sortCommand");
				Pattern p = Pattern.compile("[ ]*(public|private|protected)[ ]*(static)?[ ]*(final)?[ ]*[a-zA-Z]*[ ]*(" + fieldImpl.getName() + ").*(\\n)?");
				Matcher m = p.matcher(str);
				boolean b = m.matches();
				
				if(b && strAnterior.contains("Sorting"))
					return 1;
				
				strAnterior = str;
			}
			in.close();
		} catch (IOException e) {
		}
		
		
		return 0;
	}
	private int getNumeroLinhasMap(MethodImpl methodImpl) {
		System.out.println("## METHOD " + methodImpl.getName());
//		int numeroLinhasCodePieces = 0;
//		EList<CodePiece> codePieces = methodImpl.getCodePieces();
//		if(methodImpl.getName().contains("get")){
//			System.out.println();
//		}
//		for(int i = 0; i < codePieces.size(); i++){
//			numeroLinhasCodePieces += this.getNumeroLinhasMap( (CodePieceImpl) codePieces.get(i));
//		}
		System.out.println(this.getNumberLinesOfMethod(methodImpl).split("\n").length - 1);
		return this.getNumberLinesOfMethod(methodImpl).split("\n").length - 1;
		
//		return numeroLinhasCodePieces;
	}
	
	private String getNumberLinesOfMethod(MethodImpl methodImpl) {
		File file = getFileOfPath(methodImpl.getPath());
		
		String metodo = getMethodOfFile(methodImpl, file);

		return metodo;
	}
	private String getMethodOfFile(MethodImpl methodImpl, File file) {
		String metodo = "";
		
		String nameMetodo = " " + methodImpl.getName().substring(
				0, 
				methodImpl.getName().indexOf("(")+1
			);
		
		String strAnterior = "";
		try {
			BufferedReader in = new BufferedReader(new FileReader(file));
			String str;
			while (in.ready()) {
				str = in.readLine();
				
				if(str.contains(nameMetodo)){
					if(strAnterior.contains("Sorting")){
						contador.init();
						
						metodo += strAnterior + "\n";
						int countChaves = 0;
						str = contador.getRealLine(str);
						metodo += str + "\n";
						countChaves += (str + " ").split("\\{").length - 1;
						countChaves -= (str + " ").split("\\}").length - 1;
						if(countChaves == 0){
							in.close();
							return metodo;
						}
						while (in.ready()) {
							str = in.readLine();
							metodo += str + "\n";
							countChaves += (str + " ").split("\\{").length - 1;
							countChaves -= (str + " ").split("\\}").length - 1;
							
							if(countChaves == 0){
								in.close();
								return metodo;
							}
						}
					}
				}
				
				strAnterior = str;
			}
			in.close();
		} catch (IOException e) {
		}
		
		return metodo;
	}

	private File getFileOfPath(String pathFile){
		pathFile = pathFile.replace(".", "/") + ".java";
		pathFile = project.getLocationURI().toString().substring(
				project.getLocationURI().toString().indexOf("/")
			) + "/src/" + pathFile;
		
		File file = new File(pathFile);
		
		return file;
	}

	/**
	 * Retona o numero de linhas que a LPS tem.
	 * 
	 * @param contador
	 * @return
	 */
	private int getTotalLinhasLPS(){
		int total = 0;
		
		String pathDir = getDirectoryOfLPS();
		File diretorioRaiz = new File( pathDir );
		
		List<File> files = getFilesOfDirectory(diretorioRaiz);
		
		for(File file: files) {
			total += contador.getNumberLines(file);
		}
		
		return total;
	}
	/**
	 * Retorna o diretorio da LPS
	 * 
	 * @return
	 */
	private String getDirectoryOfLPS() {
		String pathDir = project.getLocationURI().toString();
		pathDir = pathDir.substring(
				pathDir.indexOf("/")
			);
		
		return pathDir;
	}
	/**
	 * Pegar todos os .java do diretorio passado por parametro
	 * 
	 * @param directory
	 * @return
	 */
	private List<File> getFilesOfDirectory(File directory){
		List<File> files = new ArrayList<File>();
		List<File> diretorios = new ArrayList<File>();
		diretorios.add(directory);
		
		while(!diretorios.isEmpty()) {
			File dir = diretorios.remove(0);
			
			File fList[] = dir.listFiles();
			
			for(File file : fList){
				if(file.isDirectory()){
					diretorios.add(file);
				} else {
					if(file.getPath().endsWith(".java")) {
						files.add(file);
					}
				}
			}
		}
		
		return files;
	}
	
	public String toString() {
		String str = "";
		
		for(String feature : this.numberLinesOfFeatures.keySet()){
			str += feature + ": ";
			str += this.numberLinesOfFeatures.get(feature) + "\n";
		}
		
		return str;
	}

	public static void main(String[] args) {
		String str = " public static final Command sortCommand = new Command(\"Sort by Views\", Command.ITEM, 1);";
		Pattern p = Pattern.compile("[ ]*(public|private|protected)[ ]*(static)?[ ]*(final)?[ ]*[a-zA-Z]*[ ]*(" + "sortCommand" + ").*(\\n)?");
//		Pattern p = Pattern.compile("[ ]*(public|private|protected)[ ]*(static)?[ ]*(final)?[ ]*[a-zA-Z]*[ ]*" + "sortCommand");
		Matcher m = p.matcher(str);
		boolean b = m.matches();
		
		Contador contador = new Contador();
		
		int count = 0;
		count += contador.getNumberLines(
				"/Users/luan/Documents/workspace/br.pucrio.inf.les.genarch/src-br.pucrio.inf.les.genarch/br/pucrio/inf/les/SPLAnalyser/impl/squidplugin/query/visitor/VisitorMetricTamanho.java"
			);
		
		System.out.println("O numero de linhas ate agora eh: " + count);
		
//		String path = "/Users/luan/Documents/workspace/br.pucrio.inf.les.genarch/src-br.pucrio.inf.les.genarch/br/pucrio/inf/les/SPLAnalyser/impl/squidplugin/query/visitor/VisitorMetricTamanho.java";
//		String path = "/Users/luan/Documents/workspace/br.pucrio.inf.les.genarch/src-br.pucrio.inf.les.genarch/br/pucrio/inf/les/SPLAnalyser/impl/squidplugin/query/tamanho/ProcessadorLinha.java";
		
	}

	public Map<String, List<String>> getMappingPacotes() {
		return mappingPacotes;
	}

	public void setMappingPacotes(Map<String, List<String>> mappingPacotes) {
		this.mappingPacotes = mappingPacotes;
	}

	public Map<String, List<Mapping>> getMappingClasses() {
		return mappingClasses;
	}

	public void setMappingClasses(Map<String, List<Mapping>> mappingClasses) {
		this.mappingClasses = mappingClasses;
	}

	public int getLoc() {
		return loc;
	}

	public void setLoc(int loc) {
		this.loc = loc;
	}

	public Explorador getExplorador() {
		return explorador;
	}

	public void setExplorador(Explorador explorador) {
		this.explorador = explorador;
	}

	public int getLocFeature() {
		return locFeature;
	}

}
